package fr.paris.sud.com;

import java.awt.Color;
import java.awt.EventQueue;
import java.awt.geom.Point2D;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Map;

import javax.swing.JComponent;
import javax.swing.JFrame;
import javax.swing.JPanel;

import fr.lri.swingstates.animations.*;
import fr.lri.swingstates.canvas.CElement;
import fr.lri.swingstates.canvas.CEllipse;
import fr.lri.swingstates.canvas.CImage;
import fr.lri.swingstates.canvas.CShape;
import fr.lri.swingstates.canvas.CStateMachine;
import fr.lri.swingstates.canvas.Canvas;
import fr.lri.swingstates.events.VirtualEvent;
import fr.lri.swingstates.sm.State;
import fr.lri.swingstates.sm.StateMachine;
import fr.lri.swingstates.sm.Transition;
import fr.lri.swingstates.sm.transitions.*;

public class BoomerangGuardTest extends JPanel{

	//private JFrame frame;
	private final static int WIDTH = 1200;
	private final static int HEIGHT = 800;
	private final static int MIN_SPEED = 4;
	private Canvas canvas;
	private int lastX, lastY, secondLastX, secondLastY = -1;
	private long tick = System.currentTimeMillis();
	private double mouseSpeed = 0;
	protected CStateMachine throwInteraction;
	protected CStateMachine catchInteraction;
	protected CStateMachine gridSnapInteraction;
	private HashMap<Integer, ItemAway> itensAway = new HashMap<Integer, BoomerangGuardTest.ItemAway>();
	private HashMap<Integer, ItemGhost> itensGhosts = new HashMap<Integer, BoomerangGuardTest.ItemGhost>();
	private CElement commomElement = null; //communication shared element between both the machines
	private VirtualGrid fileSystem;
	/**
	 * Launch the application.
	 */
	public static void main(String[] args) {
		EventQueue.invokeLater(new Runnable() {
			public void run() {
				try {
					JFrame frame = new JFrame();
					frame.setBounds(100, 50, 1200, 800);
					frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);					

					JComponent boomgt = new BoomerangGuardTest();
					frame.setContentPane(boomgt);

					frame.setVisible(true);
				} catch (Exception e) {
					e.printStackTrace();
				}
			}
		});
	}

	/**
	 * Create the application.
	 */
	public BoomerangGuardTest() {
		initialize();
	}

	/**
	 * Initialize the contents of the frame.
	 */
	private void initialize() {

		canvas = new Canvas(WIDTH,HEIGHT);
		CImage img1 = canvas.newImage(50, 600,"18234741.jpg");
		CImage img2 = canvas.newImage(300, 600,"18258086.jpg");
		CImage img3 = canvas.newImage(550, 600,"18456397.jpg");
		CImage img4 = canvas.newImage(800, 600,"20723592.jpg");

		img1.addTag("draggable");
		img2.addTag("draggable");
		img3.addTag("draggable");
		img4.addTag("draggable");

		//
		fileSystem = new VirtualGrid(40, 5, 50, 128, 128, WIDTH, HEIGHT);

		this.createStateMachines();
		canvas.attachSM((CStateMachine)throwInteraction, true);
		canvas.attachSM((CStateMachine)catchInteraction, true);
		canvas.attachSM((CStateMachine)gridSnapInteraction, true);

		throwInteraction.addStateMachineListener(catchInteraction);		
		catchInteraction.addStateMachineListener(throwInteraction);

		this.add(canvas);
	}


	public void createStateMachines() {
		// CStateMachine is a subclass of StateMachine that provides 
		// events to monitor a Canvas widget 
		throwInteraction = new CStateMachine() {

			public CElement toMove = null;
			Point2D lastPoint = null;

			//State S0 Boomerang state machine
			public State startS0 = new State() {

				Transition clickOnIcon = new PressOnTag("draggable", BUTTON1, ">> dragS1") {
					public void action() {
						toMove = getShape();
						lastPoint = getPoint();
					}
				};

				public void enter(){ }

				Transition catchItem = new Event("catchItem", ">> dragS1"){
					public void action(){
						toMove = commomElement;
						lastPoint = new Point2D.Double(canvas.getMousePosition().x, canvas.getMousePosition().y);
					}
				};

			};

			//State S1 - Dragging an object
			public State dragS1 = new State() {

				public void enter() {
					lastX = canvas.getMousePosition().x;
					lastY = canvas.getMousePosition().y;
					mouseSpeed = 0;
					tick = System.currentTimeMillis();
					toMove.setOutlinePaint(Color.RED);
				}

				Transition releaseIcon = new Release(BUTTON1, ">> startS0") {
					public void action(){
						toMove.translateBy(lastPoint.getX() - getPoint().getX(), lastPoint.getY() - getPoint().getY());
					}
				};				

				Transition dragWithSpeed = new Drag(BUTTON1, ">> dragS2") {
					public void action() {
						toMove.translateBy(getPoint().getX() - lastPoint.getX(), getPoint().getY() - lastPoint.getY());
						lastPoint = getPoint();
						mouseSpeed = calculateMouseSpeed((int)lastPoint.getX(),(int)lastPoint.getY(), System.currentTimeMillis());
					}
					public boolean guard(){
						return mouseSpeed > MIN_SPEED;
					}
				};

				Transition drag = new Drag(BUTTON1) {
					public void action() {
						toMove.translateBy(getPoint().getX() - lastPoint.getX(), getPoint().getY() - lastPoint.getY());
						lastPoint = getPoint();
						mouseSpeed = calculateMouseSpeed((int)lastPoint.getX(),(int)lastPoint.getY(), System.currentTimeMillis());
					}
				};

				public void leave() {
					toMove.setOutlinePaint(Color.BLACK);
				}
			};

			//State S2 - dragging an object with enough speed to throw it
			public State dragS2 = new State(){

				public void enter(){
					toMove.setOutlinePaint(Color.GREEN);
				}


				Transition throwIcon = new Release(BUTTON1, ">> startS0") {
					public void action(){
						//thrown away animation
						GeometricVector vec = new GeometricVector(lastX - secondLastX, lastY - secondLastY);
						ItemAway itemAway = new ItemAway(toMove, vec);
						itensAway.put(toMove.hashCode(), itemAway);

						Animation anim = new AnimationRotateBy(15) {
							//get the vector out of the mouse movement
							public double deltaX = lastX - secondLastX;
							public double deltaY = lastY - secondLastY;
							@Override
							public void step(double t) {

								//normalize the vector to lenght 1
								double vectorLength = Math.sqrt(Math.pow(deltaX, 2) + Math.pow(deltaY, 2));								
								deltaX = deltaX / vectorLength;
								deltaY = deltaY / vectorLength;

								//scale the vector to determine the speed of the animation
								deltaX *= 50;
								deltaY *= 50;

								getAnimated().translateBy(deltaX, deltaY);								
								getAnimated().rotateBy(t);
								setLapDuration(200);

								//stop and disappear when off the screen
								if(getAnimated().getCenterX() < 0 || getAnimated().getCenterY() < 0
										|| getAnimated().getCenterX() > WIDTH || getAnimated().getCenterY() > HEIGHT){
									this.stop();
									getAnimated().setTransparencyFill(0);
									getAnimated().setTransparencyOutline(0);
								}
							}
						};

						itemAway.setAnimation(anim);
						toMove.animate(anim);						
						toMove.removeTag("draggable");//remove draggable to deactivate click while the animation goes

						//creat mark to indicate the thrown object
						ItemGhost itemMark = new ItemGhost(toMove.hashCode(), canvas.newEllipse(lastX, lastY, 25, 25));
						itemMark.visualization.setTransparencyFill(0.2f);

						Animation animTakeItemBack = new AnimationScaleTo(1.2, 1.2);						
						animTakeItemBack.setNbLaps(Animation.INFINITE_NUMBER_OF_LAPS);
						animTakeItemBack.setLapDuration(300);
						animTakeItemBack.setDelay(20);

						itemMark.visualization.animate(animTakeItemBack);
						itemMark.setAnimation(animTakeItemBack);
						itemMark.visualization.addTag("away");

						itensGhosts.put(itemMark.visualization.hashCode(), itemMark);
					}
				};

				Transition dragWithoutSpeed = new Drag(BUTTON1) {
					public void action() {
						toMove.translateBy(getPoint().getX() - lastPoint.getX(), getPoint().getY() - lastPoint.getY());
						lastPoint = getPoint();
						mouseSpeed = calculateMouseSpeed((int)lastPoint.getX(),(int)lastPoint.getY(), System.currentTimeMillis());
						armTimer(25, false);
					}
					public boolean guard(){
						return mouseSpeed <= MIN_SPEED;
					}
				};

				Transition dragWithSpeed = new Drag(BUTTON1) {
					public void action() {
						toMove.translateBy(getPoint().getX() - lastPoint.getX(), getPoint().getY() - lastPoint.getY());
						lastPoint = getPoint();
						mouseSpeed = calculateMouseSpeed((int)lastPoint.getX(),(int)lastPoint.getY(), System.currentTimeMillis());
					}
				};

				Transition moveBackToS1 = new TimeOut(">> dragS1"){
					public void action(){

					}
				};

				public void leave() {
					toMove.setOutlinePaint(Color.BLACK);
				}
			};

		};

		catchInteraction = new CStateMachine(){


			CElement toMove = null;
			Point2D lastPoint = null;

			//State S0 Boomerang state machine
			public State startS0 = new State() {

				Transition enterOnMark = new EnterOnTag("away", ">> previewS1") {
					public void action() {
						toMove = getShape();
						//						lastPoint = getPoint();
					}
				};

			};

			public State previewS1 = new State() {

				public void enter(){					
					final ItemGhost ghost = itensGhosts.get(toMove.hashCode());
					final ItemAway itemCaught = itensAway.get(ghost.parentId);
					CElement vis = itemCaught.visualization;

					itemCaught.animation.stop();

					Animation animCatch = new AnimationRotateBy(15){
						GeometricVector motionVector = new GeometricVector((int)(ghost.visualization.getCenterX() - itemCaught.visualization.getCenterX()),
								(int)(ghost.visualization.getCenterY() - itemCaught.visualization.getCenterY()));

						@Override
						public void step(double t) {
							//to make the object reappear and come back with the same direction
							getAnimated().translateBy(motionVector.X*50, motionVector.Y*50);
							getAnimated().setTransparencyFill(1);
							getAnimated().setTransparencyOutline(1);
							getAnimated().rotateBy(t);
							setLapDuration(200);

							if(getAnimated().intersects(ghost.visualization) != null){
								getAnimated().translateTo(ghost.visualization.getCenterX(), ghost.visualization.getCenterY());
								this.stop();
								getAnimated().rotateTo(0);//to straighten the object
							}
						}
					};

					itemCaught.setAnimation(animCatch);
					vis.animate(animCatch);

				}

				Transition leaveMark = new LeaveOnTag("away", ">> startS0"){
					public void action(){
						final ItemGhost ghost = itensGhosts.get(toMove.hashCode());
						final ItemAway itemCaught = itensAway.get(ghost.parentId);
						CElement vis = itemCaught.visualization;

						if(Math.abs(itemCaught.visualization.getCenterX() - ghost.visualization.getCenterX()) > 20
								&& Math.abs(itemCaught.visualization.getCenterX() - ghost.visualization.getCenterX()) > 20){
							itemCaught.animation.stop();
						}

						Animation animRethrow = new AnimationRotateBy(15){
							GeometricVector motionVector = itemCaught.motionVector;							

							@Override
							public void step(double t) {								
								getAnimated().translateBy(motionVector.X*50, motionVector.Y*50);								
								getAnimated().rotateBy(t);
								setLapDuration(200);

								//stop and disappear when off the screen
								if(getAnimated().getCenterX() < 0 || getAnimated().getCenterY() < 0
										|| getAnimated().getCenterX() > WIDTH || getAnimated().getCenterY() > HEIGHT){
									getAnimated().setTransparencyFill(0);
									getAnimated().setTransparencyOutline(0);
									this.stop();
								}
							}
						};
						itemCaught.setAnimation(animRethrow);
						vis.animate(animRethrow);
					}
				};

				Transition clickMark = new PressOnTag("away", BUTTON1, ">> startS0"){
					public void action(){						
						ItemGhost ghost = itensGhosts.get(toMove.hashCode());
						ItemAway item = itensAway.get(ghost.parentId);

						if(Math.abs(item.visualization.getCenterX() - ghost.visualization.getCenterX()) < 5
								&& Math.abs(item.visualization.getCenterY() - ghost.visualization.getCenterY()) < 5){

							item.visualization.addTag("draggable");
							itensGhosts.get(toMove.hashCode()).visualization.remove();
							itensGhosts.get(toMove.hashCode()).animation.stop();
							itensGhosts.get(toMove.hashCode()).deleteAnimation();

							itensGhosts.remove(toMove.hashCode());
							commomElement = item.visualization;
							fireEvent(new VirtualEvent("catchItem"));
						}
					}
				};

			};
		};

		gridSnapInteraction = new CStateMachine(){
			GridNode gridPoint;
			CElement toMove = null;

			State idleGrid = new State() {

				public void enter(){
					gridPoint = null;
				}

				Transition closeToGrid = new DragOnTag("draggable", BUTTON1, ">> snapGrid") {

					public void action() {
						toMove = getShape();
						gridPoint.highlight();

					}
					public boolean guard(){
						Point2D actualPoint = getPoint();
						gridPoint = fileSystem.closestPoint(actualPoint);

						if(gridPoint == null)
							return false;

						return true;
					}
				};
			};

			State snapGrid = new State(){

				Transition snapToGrid = new Release(BUTTON1, ">> idleGrid"){
					public void action(){
						if(gridPoint != null){
							toMove.translateTo(gridPoint.vis.getCenterX(), gridPoint.vis.getCenterY());
							gridPoint.unhighlight();
						}
					}
					public boolean guard(){
						return mouseSpeed < 1.0;
					}
				};


				Transition noPoint = new DragOnTag("draggable", BUTTON1, ">> idleGrid") {
					GridNode newGridPoint;

					public void action() {
						gridPoint.unhighlight();						
					}

					public boolean guard(){
						Point2D actualPoint = getPoint();
						newGridPoint = fileSystem.closestPoint(actualPoint);

						if(newGridPoint == null)
							return true;

						return false;
					}
				};

				Transition closeToOtherPoint = new DragOnTag("draggable", BUTTON1, ">> snapGrid") {
					GridNode newGridPoint;

					public void action() {
						gridPoint.highlight();						
					}

					public boolean guard(){
						Point2D actualPoint = getPoint();
						newGridPoint = fileSystem.closestPoint(actualPoint);

						if(newGridPoint != null && !newGridPoint.equals(gridPoint)){
							gridPoint.unhighlight();
							gridPoint = newGridPoint;
							return true;
						}						
						return false;
					}
				};

			};
		};
	}


	protected double calculateMouseSpeed(int newX, int newY, long newtick) {

		if(lastX == -1)  {
			lastX = newX;
			lastY = newY;
			tick = newtick;
			return 0;
		}

		long time = newtick - tick;
		time = time != 0 ? time : time + 1; //to avoid division per zero

		double distance = Math.sqrt(Math.pow(newX-lastX, 2)
				+
				Math.pow(newY-lastY, 2));
		double speed = distance/time;

		//		System.out.println("lastX: " +lastX + " newX: "+newX);
		//		System.out.println("lastY: " +lastY + " newY: "+newX);
		//		System.out.println("distance: "+ distance);
		//		System.out.println("speed: "+ mouseSpeed);

		secondLastX = lastX;
		secondLastY = lastY;
		lastX = newX;
		lastY = newY;
		tick = newtick;		

		//		if (distance > 20) return 0; //if the user is just trying to move the object for a distance location
		return speed;
	}	

	class ItemGhost {
		int parentId;
		CEllipse visualization;
		Animation animation;
		public ItemGhost(int parentId, CEllipse vis){
			this.parentId = parentId;
			this.visualization = vis;
		}

		public void setAnimation(Animation anim){
			this.animation = anim;
		}

		public void deleteAnimation(){
			this.animation = null;
		}
	}

	class ItemAway {
		CElement visualization;
		GeometricVector motionVector;
		Animation animation;

		public ItemAway (CElement vis, GeometricVector vec){
			this.visualization = vis;
			this.motionVector = vec;
		}

		public void setAnimation(Animation anim){
			this.animation = anim;
		}

		public void deleteAnimation(){
			this.animation = null;
		}

	}


}
